#include "ptwrap.hpp"
#include <stdio.h>

// [[ex03aa]]
// DEBUG descr The basic semaphore.
class semaphore
{
public:
	semaphore(unsigned initval) :
		value_(initval)
	{ }

	int wait(unsigned n = 1)
	{
		pw::lockmutex lm(cond_);
		while(value_ < n)
			cond_.wait();
		value_ -= n;
		return 0;
	}
	int trywait(unsigned n = 1)
	{
		pw::lockmutex lm(cond_);
		if(value_ < n)
			return ETIMEDOUT;
		value_ -= n;
		return 0;
	}
	int timedwait(unsigned n, 
		const struct timespec &abstime)
	{
		pw::lockmutex lm(cond_);
		while(value_ < n) {
			if (cond_.timedwait(abstime) == ETIMEDOUT)
				return ETIMEDOUT;
		}
		value_ -= n;
		return 0;
	}
	int signal(unsigned n = 1)
	{
		pw::lockmutex lm(cond_);
		value_ += n;
		cond_.broadcast();
		return 0;
	}

protected:
	// contains both condition variable and a mutex
	pw::pmcond cond_; 
	// current value of the semaphore, always positive
	unsigned value_; 
};
// END

// DEBUG {

semaphore sem(0);

class singles1 : public pw::pwthread
{
public:
	void *execute()
	{
		for (int i = 0; i < 10; i++) {
			sem.signal();
			if (sem.trywait() == ETIMEDOUT) {
				fprintf(stderr, "trywait failed\n");
				sem.wait();
			}
		}
		sem.signal();
		fprintf(stderr, "s1 finished\n");
		return 0;
	}
};

class singles2 : public pw::pwthread
{
public:
	void *execute()
	{
		for (int i = 0; i < 10; i++) {
			sem.wait();
			sem.signal();
		}
		fprintf(stderr, "s2 finished\n");
		return 0;
	}
};

class tens : public pw::pwthread
{
public:
	void *execute()
	{
		sem.wait(10);
		fprintf(stderr, "tens succeeded\n");
		sem.signal(10);
		return 0;
	}
};

int main()
{
	tens t;
	singles1 s1[10];
	singles2 s2[10];

	t.start();
	for (int i = 0; i < 10; i++) {
		s1[i].start();
		s2[i].start();
	}
	for (int i = 0; i < 10; i++) {
		s1[i].join();
		s2[i].join();
	}
	t.join();

	struct timespec endtime;
	clock_gettime(CLOCK_REALTIME, &endtime); // the current time
	if (sem.timedwait(11, endtime) == ETIMEDOUT) {
		fprintf(stderr, "timed out as expected\n");
		endtime.tv_sec += 1;
		if (sem.timedwait(10, endtime) == 0)
			fprintf(stderr, "no timeout as expected\n");
	}

	return 0;
}
// DEBUG }
/* Sample output:
s1 finished
s2 finished
s1 finished
s2 finished
s1 finished
s1 finished
s2 finished
s2 finished
s2 finished
s1 finished
s1 finished
s2 finished
s1 finished
s2 finished
s2 finished
s1 finished
s2 finished
s1 finished
tens succeeded
s1 finished
s2 finished
timed out as expected
no timeout as expected
*/
